#ifndef NO_MIC
#include "../HardwareMicrophone.h"

#include "../../../Debug/Logging.h"

#include "driver/i2s_pdm.h"

#define SAMPLE_RATE 16000
#define DATA_SIZE 1024
#define MAX_BUFFER_SIZE 4096

static i2s_chan_handle_t rx_chan;
static uint8_t microphonedata0[MAX_BUFFER_SIZE];
static uint32_t data_offset = 0;
static std::vector<std::function<bool(uint8_t *, size_t)>> callbacks;
static bool deviceConfigured = false;

#define LOG_MIC "MIC"

namespace Devices
{
    HardwareMicrophone Mic;
}

HardwareMicrophone::HardwareMicrophone()
{
}

void HardwareMicrophone::begin(Preferences &prefs)
{
    do
    {
        /* Setp 1: Determine the I2S channel configuration and allocate RX channel only
         * The default configuration can be generated by the helper macro,
         * but note that PDM channel can only be registered on I2S_NUM_0 */
        i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
        if (i2s_new_channel(&rx_chan_cfg, NULL, &rx_chan) != ESP_OK)
        {
            Debug::Log.error(LOG_MIC, "i2s_new_channel error");
            break;
        }

        /* Step 2: Setting the configurations of PDM RX mode and initialize the RX channel
         * The slot configuration and clock configuration can be generated by the macros
         * These two helper macros is defined in 'i2s_pdm.h' which can only be used in PDM RX mode.
         * They can help to specify the slot and clock configurations for initialization or re-configuring */
        i2s_pdm_rx_config_t pdm_rx_cfg = {
            .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
            /* The data bit-width of PDM mode is fixed to 16 */
            .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
            .gpio_cfg = {
                .clk = (gpio_num_t)I2S_SPM1423_CLK,
                .din = (gpio_num_t)I2S_SPM1423_DATA,
                .invert_flags = {
                    .clk_inv = false,
                },
            },
        };

        pdm_rx_cfg.slot_cfg.slot_mask = I2S_PDM_SLOT_RIGHT;

        if (i2s_channel_init_pdm_rx_mode(rx_chan, &pdm_rx_cfg) != ESP_OK)
        {
            Debug::Log.error(LOG_MIC, "i2s_channel_init_pdm_rx_mode error");
            break;
        }

        /* Step 3: Enable the rx channels before reading data */
        if (i2s_channel_enable(rx_chan) != ESP_OK)
        {
            Debug::Log.error(LOG_MIC, "i2s_channel_init_pdm_rx_mode error");
            break;
        }

        Debug::Log.info(LOG_MIC, "Microphone has been set up");
        deviceConfigured = true;

    } while (false);
}

void HardwareMicrophone::loop(Preferences &prefs)
{
    if (capture && deviceConfigured && !callbacks.empty())
    {
        data_offset = 0;

        while (data_offset < MAX_BUFFER_SIZE)
        {
            size_t bytes_read = 0;
            if (i2s_channel_read(rx_chan, (microphonedata0 + data_offset), DATA_SIZE, &bytes_read, 1000) == ESP_OK && bytes_read != 0)
            {
                data_offset += bytes_read;
            }
            else
            {
                Debug::Log.error(LOG_MIC, "Error getting mic samples");
                break;
            }
        }

        // send the PCM data to anyone who wants it
        if (data_offset != 0)
        {
            for (const auto &func : callbacks)
            {
                func((uint8_t *)microphonedata0, data_offset);
            }
        }
    }
}

void HardwareMicrophone::setCallback(const std::function<bool(uint8_t *, const size_t)> &callback)
{
    callbacks.emplace_back(callback);
}
#endif